#!/usr/bin/env python

#   Trolls Outta Luckland v1.1.0
#   Copyright 2009-2010, Gummbum
#
#   This file is part of Trolls Outta Luckland.
#
#   Trolls Outta Luckland is free software: you can redistribute it
#   and/or modify it under the terms of the GNU General Public License
#   as published by the Free Software Foundation, either version 3 of
#   the License, or (at your option) any later version.
#
#   Trolls Outta Luckland is distributed in the hope that it will be
#   useful, but WITHOUT ANY WARRANTY; without even the implied warranty
#   of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with Trolls Outta Luckland.  If not, see
#   <http://www.gnu.org/licenses/>.

__version__ = '1.1.0'

### Python imports
from math import cos, radians, sin
import os
from os.path import join as join_path
import random
from random import randrange
import sys

try:
    ### Pygame imports
    import pygame
    ### Game library imports
    import enemy
    import game_globals
    from game_globals import *
    import gumm_sound
    from pixelperfect import *
    import sprite_strip_anim
    from sprite_strip_anim import SpriteStripAnim
    from utils import (
        calc_angle, calc_circumference_point, calc_line, constrain, distance,
        game_to_mouse, mouse_to_game, load_image,
    )
    from vec2d import *
except ImportError, e:
    print 'ImportError: %s' % e
    print '    in file "%s"' % os.path.abspath(sys.argv[0])

MOUSE_LEAD = 15

game = None
jukebox = None

# Ship angle size of 1 makes turns *very* smooth at the significant cost of
# initial compile time and memory. If you change this you'll need to edit
# bin/make-hitmasks.py and run it to regen the hitmasks module code.
_SHIP_SPRITE_SHEET = join_path('image','SPACE1x150.png')
_SHIP_ANGLE_SIZE = 2

def iround(n):
    return int(round(n))

class RingBuffer:
    def __init__(self, size):
        self.data = [None for i in xrange(size)]
    def append(self, *args):
        for arg in args:
            self._append(arg)
    def get(self):
        return self.data
    def _append(self, x):
        self.data.pop(0)
        self.data.append(x)

class LaserBlast(pygame.sprite.Sprite):
    def __init__(self, ship, dir):
        pygame.sprite.Sprite.__init__(self)
        self.speed = LASER_SHOT_SPEED * game.speed()
        self.image,self.rect = load_image(
            join_path('image','laserblast_blue.png'), -1)
        if dir == SHOOTING_FORE:
            self.angle = ship.angle % 360
        else:
            self.angle = ship.angle - 180 % 360
        self.image = pygame.transform.rotate(self.image, ship.angle)
        self.rect = self.image.get_rect()
        self.rect.center = ship.rect.center
        self.centerf = vec2d(ship.fcenter)
        self.hitmask = get_colorkey_hitmask(self.image, self.rect, -1)
    def power_up(self, others):
        for other in others:
            other.hit(self)
    def update(self, *args):
        self.centerf.x += -sin(radians(self.angle)) * self.speed
        self.centerf.y += -cos(radians(self.angle)) * self.speed
        self.rect.centerx = iround(self.centerf.x)
        self.rect.centery = iround(self.centerf.y)
        if not game.window.rect.collidepoint(self.rect.center):
            self.kill()

class LaserCannon():
    def __init__(self, ship):
        self.ship = ship
        # fire rate throttles
        self.fire_rate = FIRE_RATE * game.speed() # frames
        self.input_sync = INPUT_RATE * game.speed()
        self.input_ready = True
        # mouse input queue
        self.queue = []
        self.queue_depth = MOUSE_QUEUE_DEPTH
        # shots on screen
        self.shots = pygame.sprite.Group()
        self.max_shots = LASER_SHOTS
    def tick(self):
        self.input_sync -= 1
        if self.input_sync <= 0:
            self.input_ready = True
        if self.fire_rate > 0:
            self.fire_rate -= 1
        if len(self.queue) > 0:
            if self.fire_rate == 0 and \
            len(self.shots.sprites()) < self.max_shots:
                self._fire()
    def fire(self, dir):
        if dir & (SHOOTING_FORE|SHOOTING_AFT):
            if len(self.queue) < self.queue_depth and self.input_ready and \
            len(self.shots.sprites()) < self.max_shots:
                self.queue.append(dir)
                self.input_ready = False
                self.input_sync = INPUT_RATE
    def _fire(self):
        jukebox['player fires laser'].play()
        rect = self.ship.rect
        blast = LaserBlast(self.ship, self.queue.pop(0))
        self.shots.add(blast)
        groups = game.groups
        groups.add(blast, layer='player_shots')
        self.fire_rate = FIRE_RATE # frames

class ShipExplodes(pygame.sprite.Sprite):
    def __init__(self, pos):
        pygame.sprite.Sprite.__init__(self)
        self.pos = pos
        self.anim = (
            SpriteStripAnim(join_path('image','Explode3x200.png'),                (1,1,96,96), 4, -1, False, game.fps.fps/12,
                name='player_explodes1') +
            SpriteStripAnim(join_path('image','Explode3x200.png'),
                (1,97,96,96), 4, -1, False, game.fps.fps/12,
                name='player_explodes2'))
        self.anim.iter()
        jukebox['player explodes'].play()
    def update(self, *args):
        try:
            self.image = self.anim.next()
        except StopIteration:
            self.kill()
        self.rect = self.image.get_rect()
        self.rect.center = self.pos

class Ship(pygame.sprite.Sprite):
    def __init__(self):
        global game, jukebox
        game = game_globals.game
        jukebox = gumm_sound.jukebox
        jukebox.load_sound(join_path('sound','laser_26667.ogg'),
            'player fires laser', volume=0.7, sound_type=SOUND_FX)
        jukebox.load_ascii_file(join_path('sound','player_explodes.dat'),
            'player explodes', volume=0.8, sound_type=SOUND_FX)
        pygame.sprite.Sprite.__init__(self)
        self.angle = 0
        self.anim = SpriteStripAnim(_SHIP_SPRITE_SHEET, (10,60,45,45), 3,
            colorkey=-1, hitmasks=False, angles=range(0,361,_SHIP_ANGLE_SIZE),
            name='player')
        self.anim.load_hitmasks('player', 3)
        self.image,self.hitmask = self.anim.get_angle(0,0)
        self.rect = self.image.get_rect()
        game_rect = game.window.rect
        self.rect.center = (
            game_rect.centerx, game_rect.height - self.rect.height)
        self.fcenter = self.rect.center
        self.move_to = self.rect.center
        self.speed = PLAYER_SPEED * game.speed()
        # a trail smooths out turns, calming sprite jitters
        self.trail = RingBuffer(10)
        self.trail.append(*[self.rect.center]*10)
        #
        self.state = 0
        self.weapon = LaserCannon(self)
        self.turn()
    def update(self, *args):
        self.weapon.tick()
        self.move_to = game_globals.game.pointer.get_pos()
        rect = self.rect
        # If mouse moved, plot a new course
        self.turn()
        if rect.center != self.move_to:
            if distance(self.fcenter, self.move_to) <= self.speed:
                rect.center = self.fcenter = self.move_to
            else:
                angle = calc_angle(self.fcenter, self.move_to)
                move_to = calc_circumference_point(
                    self.speed, self.fcenter, angle)
                self.fcenter = move_to
                rect.center = int(round(move_to[0])), int(round(move_to[1]))
            if distance(rect.center, self.move_to) > MOUSE_LEAD:
                angle = calc_angle(self.fcenter, self.move_to)
                mouse = calc_circumference_point(
                    MOUSE_LEAD, self.fcenter, angle)
                pygame.mouse.set_pos(*game_to_mouse(mouse))
            self.trail.append(self.fcenter)
    def turn(self):
        if self.rect.center == self.move_to:
            pick = random.choice((0,0,0,0,0,1,1))
        else:
            pick = random.choice((0,1,1,2,2,2,2))
            move_from = self.trail.get()[0]
            degrees = calc_angle(move_from, self.move_to, to_screen=False)
            degrees = int(round(degrees/_SHIP_ANGLE_SIZE))
            degrees *= _SHIP_ANGLE_SIZE
            angle_size = _SHIP_ANGLE_SIZE * iround(16 / iround(game.speed()))
            diff = self.angle - degrees
            if diff < -180:
                diff += 360
            elif diff >= 180:
                diff -= 360
            if diff >= -180 and diff <= -angle_size:
                self.angle += angle_size
            elif diff < 180 and diff >= angle_size:
                self.angle -= angle_size
            else:
                self.angle = degrees
            if self.angle < 0:
                self.angle += 360
            elif self.angle >= 360:
                self.angle -= 360
        pos = self.rect.center
        self.image,self.hitmask = self.anim.get_angle(pick, self.angle)
        self.rect.size = self.image.get_rect().size
        self.rect.center = pos
        sys.stdout.flush()
    def hit(self, others):
        for other in others:
            if issubclass(type(other), (enemy.Enemy, enemy.LaserBlast,
                    enemy.Missile, enemy.Mine)):
                self.kill()
                other.kill()
                game.groups.add(ShipExplodes(self.rect.center),
                    layer='players', under=True)
    def power_up(self, others):
        for other in others:
            other.hit(self)
    def reset(self):
        game_rect = game.window.rect
        self.rect.center = (
            game_rect.centerx, game_rect.height - self.rect.height)
        self.fcenter = self.rect.center
        self.move_to = self.rect.center
        self.state = 0
        self.angle = 0
        self.weapon = LaserCannon(self)
        # a trail smooths out turns, a remedy for the jitters
        self.trail = RingBuffer(10)
        self.trail.append(*[self.rect.center]*10)
        self.turn()

if __name__ == '__main__':
    ## test: cycle sprite strips to check for jitters
    os.chdir('..')
    from pygame.locals import Color, KEYUP, K_ESCAPE
    pygame.init()
    clock = pygame.time.Clock()
    surf = pygame.display.set_mode((200,200))
    bg = surf.copy()
    bg.convert()
    bg.fill(Color('black'))
    anim = SpriteStripAnim(_SHIP_SPRITE_SHEET, (10,60,45,45), 3,
        colorkey=-1, hitmasks=True, angles=range(0,361,_SHIP_ANGLE_SIZE),
        name='player')
    i = 0
    while 1:
        for e in pygame.event.get():
            if e.type == KEYUP and e.key == K_ESCAPE:
                quit()
        if i == 3:
            i = 0
        surf.blit(bg, bg.get_rect())
        image,hitmask = anim.get_angle(i, 0)
        rect = image.get_rect()
        rect.topleft = 50,50
        surf.blit(image, rect)
        pygame.display.flip()
        clock.tick(2)
        i += 1
